`timescale 1ns / 1ps
/*-------------------------------------------------------------------------------------*\
FileName        : cbb_i2c_master.v
Author          ：hpy
Email           ：yuan_hp@qq.com
Date            ：2023年04月01日
Description     ：
模块需要将要发送的数据写入FIFO，FIFO的深度可以自行在调用时进行配置，默认深度为16，
模块通过 iicr0的bit1写0后写1触发一次iic操作

1.iicr0 控制寄存器
    iicr0[0]:iic使能控制，高有效     W
    iicr0[1]:写0写1触发一次iic读写操作 
    iicr0[2]:为0时写FIFO的缓冲区在经过IIC发送完毕后直接进入读或者停止，
             为1时写FIFO的缓冲区在经过IIC发送完毕后马上发送一次启动位然后进入读数据模式
             数据格式: start| 器件地址+写 | 地址/控制字 | start |  器件地址+读 |  Byte[1] ~ Byte[n] | stop  
2.iicr1 控制寄存器1
    iicr1[7:0] 用于分频时钟   (iicr1+1)*4 分频
3. iisr0 状态寄存器
    iisr0[0]: rfempty;
    iisr0[1]: wffull;
    iisr0[2]: no_ack;  //没有ack’信号 ，此时出错
    iisr0[3]: iicwr;  // 读操作还是写操作
    iisr0[4]: (state==IDEL)?1'b0:1'b1;  //模块是否忙  1:忙 0:空闲
    iisr0[5]: rfwe ; //可作为读数据中断

cbb_i2c_master #(
    .dw (8 ) ,//fifo数据宽度
    .de (16)   //fifo数据深度，verilog根据设置值，自动向上修正为2^n值
)i2c_master_u1 (
    .clk( clk ),
    .rst_n( rst_n ),
    .iicr0( ),//[7:0]  i2c master 控制寄存器0 
    .iicr1( ),//[7:0] i2c master 控制寄存器1， 用于分频时钟   (iicr1+1)*4 分频
    .write_cnt () , //[15:0] 需要写的数据字节数
    .read_cnt() , //需要读的数据字节数 
    .iisr0( ),//[7:0] i2c 状态寄存器

    .din ( ),  //写入发送fifo的数据
    .dout( ), //i2c读到的数据
    .wfwe   ( ) ,
    .rfre   ( ) ,
    .wffull ( ) ,
    .rfempty( ) ,
    // i2c master 接口
    .scl( ) , 
    .sda( )
);

v1.1      2023.10.19   hpy            添加支持sht30芯片读数据的重新触发启动位置的方法 
\*----------------------------------------------------------------------------------------*/
module cbb_i2c_master #(
    parameter dw =8  ,//fifo数据宽度
    parameter de =16   //fifo数据深度，verilog根据设置值，自动向上修正为2^n值
) (
    input               clk,
    input               rst_n,
    input   [7:0]       iicr0 ,//i2c master 控制寄存器0
    input   [7:0]       iicr1 ,//i2c master 控制寄存器1， 用于分频时钟   (iicr1+1)*4 分频
    input   [15:0]      read_cnt , // 需要读的字节数
    input   [15:0]      write_cnt , // 需要写的字节数据
    output  [7:0]       iisr0 ,//i2c 状态寄存器

    input   [7:0]       din,  //写入发送fifo的数据
    output  [7:0]       dout, //i2c读到的数据
    input               wfwe,
    input               rfre,
    output              wffull,
    output              rfempty,
    output    reg       interrupt , //中断信号 ， 产生一个clk的高电平
    // i2c master 接口
    output reg          scl , 
    inout               sda

);
//---------------------- param ---------------------
localparam 
    IDEL        =4'b0000 ,
    START       =4'b0001 ,
    WRITE       =4'b0011 , 
    READ        =4'b0010 , 
    ST_DONE     =4'b0110 ,
    SYNC        =4'b0100 ,
    RESTART     =4'b1100 ;


//----------------------- reg ----------------------
reg [3:0] state ; //状态机
reg [7:0] clkcnt ;
reg sda_r , arc_err; 
reg [1:0]sda_f ,pha  ;
reg stop_flag ; //检测到停止信号 

reg no_ack ; //从机没有ack 

//-----------------------wire ---------------------
wire iice  , sda_in , ena ; //iic使能控制，高有效
wire iic_stand_read ; // 1:启动标准读模式    start| 器件地址+写 | 地址/控制字 | start |  器件地址+读 |  Byte[1] ~ Byte[n] | stop  
reg iicwr ; //读操作还是写操作
reg std_read_r ; 
//inout信号处理
assign sda = (sda_r==1'b0) ? 1'b0 : 1'bz ; 
assign sda_in = sda ; 

assign iice = iicr0[0] ;   //使能IIC 控制器
assign iicstart =iicr0[1] ;  //写0写1触发一次IIC操作 
assign iic_stand_read = iicr0[2] ;

assign iisr0[0] = rfempty;
assign iisr0[1] = wffull;
assign iisr0[2] = no_ack;
assign iisr0[3] = iicwr;  // 读操作还是写操作
assign iisr0[4] = (state==IDEL)?1'b0:1'b1;  //模块是否忙  1:忙 0:空闲
assign iisr0[5] = rfwe ; //可作为读数据中断
//时钟分频
always @(posedge clk) begin
    if( iice & (|clkcnt & |state) ) 
        clkcnt <=(~|clkcnt)? iicr1 : clkcnt - 1'b1 ; 
    else 
        clkcnt <= iicr1 ; 
end 

assign  ena = iice & (~|clkcnt) ;

always @(posedge clk) begin
    if(!rst_n)
        pha <= 2'h3;
    if(iice)
        if(ena) pha <= (~|pha)? 2'h3 : pha - 1'b1 ;
    else 
        if(ena)pha <= 2'h3;
end


//发送fifo 
//wire wfwe ,wffull , wfempty  ;
wire [7:0] wfdout ;  

reg wfre,rfwe ; 
cbb_fifo #(
	.de(de), //数据深度，任意正整数，若不是2的幂，会向上自动取2的幂次值，如设置为3，那么实际深度为4
    .dw(dw)    //数据宽度，
)wffifo (
	.clk(clk),    //系统时候时钟
	.rst_n(rst_n), //复位信后 ，低有效
	.clr(~iice),    // fifo清空，高有效
	.din(din),    //输入的数据
	.we(wfwe),     //写使能 ， 高有效
	.re(wfre),     //读使能，若非空，先读数据然后使能一次，则数据出队
    .dout(wfdout), //读出的数据
    .full( wffull) ,    //fifo满则为1 
    .empty( wfempty)     //fifo空则为1
);

cbb_fifo #(
	.de(de), //数据深度，任意正整数，若不是2的幂，会向上自动取2的幂次值，如设置为3，那么实际深度为4
    .dw(dw)    //数据宽度，
)rffifo (
	.clk(clk),    //系统时候时钟
	.rst_n(rst_n), //复位信后 ，低有效
	.clr(~iice),    // fifo清空，高有效
	.din( rreg[7:0]),    //输入的数据
	.we(rfwe),     //写使能 ， 高有效
	.re(rfre),     //读使能，若非空，先读数据然后使能一次，则数据出队
    .dout( dout), //读出的数据
    .full( rffull) ,    //fifo满则为1 
    .empty( rfempty)     //fifo空则为1
);


always@(posedge clk) begin
    // sda_f <= {sda_f[0],sda} ; 
    sda_f <= {sda_f[0],sda_in} ; 

    if(iice & rst_n) begin
        // if( ~sda_f[1] & (sda_f[0]===1'hz) & (state==READ))  //仿真调试使用
        if( scl & ~sda_f[1] & sda_f[0]==1'h1 & (state==READ)) 
            stop_flag <= 1'b1 ; 
        else if(~|state )
            stop_flag <= 1'b0 ; 
        else 
            stop_flag <= stop_flag ;
    end else stop_flag <= 1'b0;
end

reg [3:0] bitcnt ; 
reg [8:0] treg ;
reg [ 7:0] rreg ; 
reg [15:0] write_cnt_r , read_cnt_r ;  // 读或者写的字节数
reg [1:0] iicstart_f ; // 打拍
//测试
always @(posedge clk ) begin
    if( ~iice | ~rst_n) begin
        state <= IDEL ; 
        scl         <= 1'b1 ; 
        sda_r       <= 1'b1 ; 
        bitcnt      <= 4'h0 ; 
        wfre        <= 1'b0 ; 
        iicwr       <= 1'b0;
        treg        <= 9'h0;
        rfwe        <= 1'b0;
        rreg        <= 8'h0;
        iicstart_f  <= 2'h0;
        no_ack     <= 1'b0; 
        interrupt   <= 1'b0 ; 
        std_read_r  <= 1'b0 ;
    end else begin 
        wfre <= 1'b0;
        rfwe <= 1'b0;
        iicstart_f <= {iicstart_f[0] , iicstart } ;
        interrupt <= 1'b0 ;
        case(state) 
            IDEL: 
                begin
                    scl <= 1'b1;
                    sda_r <= 1'b1;
                    treg <= 9'b1111_1111_1;
                    if ( (iicstart_f == 2'b01) && ( |write_cnt ) ) begin 
                        write_cnt_r <= write_cnt ; 
                        read_cnt_r  <= read_cnt ;
                        state <= START ; //SYNC ;
                        bitcnt <= 4'h0 ;
                        no_ack <= 1'b0;
                        std_read_r <= iic_stand_read ; 
                    end 
                end
            SYNC : //同步打拍 
                if ( ena ) begin
                    if (pha==2'h0) state <= START ; 
                end  
            START: //主机产生起始信号
                begin
                    if(ena)begin
                        case(pha)
                            2'h3: sda_r <= 1'b1 ;
                            2'h2: scl <= 1'b1;
                            2'h1: sda_r <=1'b0 ; 
                            2'h0:
                                begin
                                    scl <= 1'b0;
                                    if( ~wfempty ) begin
                                        treg[8:1] <= {wfdout[7:0] }; 
                                        treg[0] <= 1'b1 ; //1'hz;
                                        wfre  <=1'b1 ; 
                                        iicwr <= wfdout[0] ; //wffifo第一个非空认为时器件地址，最后一位为读写位
                                        bitcnt <= 4'd8;
                                        state <= WRITE ; //
                                        write_cnt_r <= (|write_cnt_r) ?  write_cnt_r - 1'b1  : write_cnt_r ;
                                    end 
                                    else begin
                                        state <= IDEL ;
                                    end
                                end
                        endcase
                    end
                end
            WRITE: //写IIC
                begin
                    if(ena)begin
                        case(pha)
                            2'h3 : scl <= 1'b0;
                            2'h2: sda_r <= treg[8];  
                            2'h1: scl <= 1'b1;
                            2'h0:
                                begin
                                    treg <= treg<<1;
                                    bitcnt <= bitcnt - 1'b1 ; 
                                    if(~|bitcnt) begin
                                        treg[8:1] <= wfdout ; 
                                        treg[0] <= 1'b1 ;//1'hz;
                                        bitcnt <= 4'd8 ; 
                                        if(sda_in) begin 
                                            no_ack <= 1'b1 ; 
                                            state <= ST_DONE ; 
                                        end 
                                        else if( ~|(write_cnt_r - std_read_r) ) begin  // 主机写自己完毕 
                                            if(std_read_r) begin 
                                                state <= START ; 
                                                scl <= 1'b0;
                                                write_cnt_r <= write_cnt_r - 1'b1 ;
                                                std_read_r <= 1'b0;
                                            end 
                                            else if ( |read_cnt_r ) begin 
												state <= READ ;
                                                treg[8:1] <= {8{1'h1}} ;// {8{1'hz}} ;
                                                if(read_cnt_r==16'h1)
                                                    treg[0] <= 1'h1; //主机应答
                                                else
                                                    treg[0] <= 1'h0; //主机应答
                                                read_cnt_r <= read_cnt_r - 1'b1 ;
                                            end else begin 
                                                state <= ST_DONE ;
                                            end 
                                        end else begin  // 继续发送剩余字节
                                            if (~wfempty) begin 
                                                write_cnt_r <= write_cnt_r - 1'b1 ;
                                                state <= WRITE ;
                                                wfre <= 1'b1;
                                                treg[8:1] <= wfdout[7:0] ;// {8{1'hz}} ;
                                                treg[0] <= 1'h1; //等待slave应答
                                            end else  state <= ST_DONE ;
                                        end
                                    end                               
                                end                        
                        endcase 
                    end
                end   
            RESTART : 
                if(ena)begin
                    case(pha)
                        2'h3: scl <= 1'b0 ;
                        2'h2: begin 
                                sda_r <= 1'b1; 
                                state <= START ;  
                            end
                    endcase
                end          
            READ: 
                begin
                    if(ena)begin
                        case(pha)
                            2'h3: scl <= 1'b0;
                            2'h2: sda_r <= treg[8];
                            2'h1: scl <= 1'b1;
                            2'h0: 
                                begin 
                                    scl <= 1'b1;
                                    treg <= {treg[7:0] , sda_in } ; //treg<<1 ; 
                                    bitcnt <= bitcnt - 1'b1 ; 
                                    if(~|bitcnt) begin
                                        treg[8:1] <= {8{1'h1}} ;// {8{1'hz}} ;
                                        if (|read_cnt_r) read_cnt_r <= read_cnt_r - 1'b1 ;
                                        
                                        if(read_cnt_r==16'h1)
                                            treg[0] <= 1'h1; //NO ACK
                                        else 
                                            treg[0] <= 1'h0; // ACK 

                                        if(~|read_cnt_r) state <= ST_DONE ;
                                        else if(stop_flag)state <= IDEL ;
                                        else state <= READ ;

                                        rreg <= {treg[7:0]};
                                        bitcnt <= 4'd8 ; 
                                        rfwe  <= 1'b1 ; 
                                    end
                                end                            
                        endcase 
                    end
                end 
            ST_DONE: //产生停止信号
                    if(ena)begin
                        case(pha)
                            2'h3 : scl <= 1'b0 ; 
                            2'h2 : sda_r <= 1'b0 ; 
                            2'h1 : scl <= 1'b1; 
                            2'h0 :
                                begin 
                                    // scl <= 1'b1 ; 
                                    sda_r <= 1'b1 ; 
                                    state <= IDEL ;
                                    interrupt <= 1'b1 ; 
                                end
                        endcase
                    end 
        endcase 
    end
end


endmodule
